package com.comfacauca.xmlc2.utils.tablas;


import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import com.google.gson.Gson;
//import org.apache.log4j.Logger;

/**
 * DataTables Editor base class for creating editable tables.
 *
 * Editor class instances are capable of servicing all of the requests that
 * DataTables and Editor will make from the client-side - specifically:<br>
 * -   Get data<br>
 * -   Create new record<br>
 * -   Edit existing record<br>
 * -   Delete existing records<br><br>
 *
 * This being the Java version of server side classes supporting DataTables,
 * these classes will work strictly using Objects with the syntax:<br>
 * <i>{"key1":"value1","key2":"value2","key3":{"subkey1":"value3","subkey2":"value4"}} or</i><br>
 * <i>{"key1":"value1","key2":"value2","key3":[{"subkey1":"value3","subkey2":"value4"}]}</i><br>
 * For the purposes of providing inserts and updates of data, this syntax
 * is mostly used and is best suited for OOP (Object Oriented Programming).<br><br> 
 * On the client side and in PHP the concept of associative arrays does not exist in Java.
 * The closest data structure resembling the associative array is the HashMap.  In fact,
 * to ensure that elements placed in HashMap came out in the same order while being processed
 * by GSON, I had to resort to using a special version of HashMap called the LinkedHashMap.
 * Therefore, I use the LinkedHashMap extensively throughout this library of classes.<br><br>
 * The Editor instance is configured with information regarding the
 * database table fields that you wish to make editable, and other information
 * needed to read and write to the database (table name for example!).<br><br>
 * 
 * Usage Example: A very basic example of using Editor to create a table with four fields.
 *    This is all that is needed on the server-side to create an editable
 *    table. The code below would be used in a Java Server Page or could be used
 *    even in a Servlet.<br><br>
 * <code>   
 * String dbName = "fcs_db";<br>
 * String[] tableNames = new String[]{"employees"};<br>
 * String pKey = "ID";<br>
 * String[] fields = new String[]{"LASTNAME","FIRSTNAME","TITLE","EMAIL_ADDRESS"};<br>
 * Database db = new Database(dbName);<br>
 * Editor editor = new Editor(db, tableNames, pKey, params);<br>
 * Field field0 = new Field(tableNames[0],fields[0],Field.Type.STRING);<br>
 * field0.setValidator(new Validate(Validate.Type.REQUIRED));<br>
 * editor.addField(field0);<br>
 * Field field1 = new Field(tableNames[0],fields[1],Field.Type.STRING);<br>
 * field1.setValidator(new Validate(Validate.Type.REQUIRED));<br>
 * editor.addField(field1);<br>
 * Field field2 = new Field(tableNames[0],fields[2],Field.Type.STRING);<br>
 * editor.addField(field2);<br>
 * Field field3 = new Field(tableNames[0],fields[3],Field.Type.STRING);<br>
 * field3.setValidator(new Validate(Validate.Type.EMAIL_REQUIRED));<br>
 * editor.addField(field3);<br>
 * editor.Process();<br>
 * out.println(editor.toJSONString());<br>
 * </code>
 */

public class Editor 
{
  /** params - encapsulates all the parameters passed from the client side */ 
  private Parameters params = null;
  /** db - A Database object used to interact with the database. */
  private Database db = null;
  /** query - A Query object used to assemble a query string for the database.*/
  private Query query = null;
  private ArrayList<Field> fields = null;
  /** Form Data - We'll assume for now it's a String[] array, representing one row of data*/
  private String[] formData = null; 
  /** not implemented yet */
  private ArrayList<Join> joins = new ArrayList<Join>();
  /** Primary Key: default is "id" */
  protected String pKey = "id";
  /** table = a String array of table names */
  private String[] table = null;
  /** where - an ArrayList of Where objects that will be used to filter records on a Query. */
  private ArrayList<WhereCondition> where = new ArrayList<WhereCondition>();
  
  /**
   * data - A 2D String[][] array containing all the data for every row in the query. 
   */
  private String[][] data = null;
  protected boolean usingSSP = false;
  /**
   * For SSP (server side processing), including the DT_RowId is optional.
   * If you set this to <i>true</i>, the primary key value for each record will be used. 
   */
  protected boolean includeRowID = false; 
  /**
   * For SSP (server side processing), including the DT_RowClass is optional.
   * If you set this to <i>true</i>, a DT_RowClass field will be included in
   * the JSON string. If using this option, optionally provide a prefix by setting
   * rowclassPrefix which will be appended to the value of an existing field, which
   * you indicate by setting the field name for rowclassFieldReference.
   */
  private boolean includeRowClass = false; 
  private String rowclassPrefix = "";
  private String rowclassFieldReference = "";
  private String parentTableAlias = "";  
  public Object output = null;
  private boolean setJoinFields = false;
  private Field conditionField = null;
  private String parameterKey = "";
//  private Logger logger;
  private static boolean loggingEnabled = true;
  

  public Editor (Database database, String[] tableNames, String primaryKey)
  {
    db = database;
    db.setEditorReference(this);
    table = tableNames;
    pKey = primaryKey;
//    logger = Logger.getLogger(Editor.class);
  }
  
  public Editor (Database database, String[] tableNames, String primaryKey, Parameters parameters)
  {
    db = database;
    db.setEditorReference(this);
    table = tableNames;
    pKey = primaryKey;
    params = parameters;
//    logger = Logger.getLogger(Editor.class);
  }  
  
  
  public static String  getFullStackTrace(Throwable throwable) {
      StringWriter sw = new StringWriter();
      PrintWriter pw = new PrintWriter(sw, true);
      throwable.printStackTrace(pw);
      return sw.toString();
 }
  
  protected static boolean isLoggingEnabled()
  {
	  return loggingEnabled;
  }
  
  /**
   * If you wish to disable logging call this method
   * @param propertiesFile
   */
  public static void disableLogging()
  {
	  loggingEnabled = false;	  
  }
  
  /**
   * 
   * Use this method on a Join when a Join has been flagged as READ ONLY. This means you've set its canWrite property to false and
   * don't want to write to the database.
   * However, in order to get any output from the join, you need to perform a lookup on the database in order to set the 
   * values on the Join fields during an Update operation.  This method isn't used much, but comes in handy when
   * you have a self referencing table.
   * @param value Set to true when wanting to set the Join fields with values
   * @param field which will become part of a WhereCondition.
   * @param paramKey This would be a key used to perform a Parameter lookup to obtain a value for the field.
   */
  public void setJoinFieldsOnUpdateAndInsert(boolean value, Field field, String paramKey)
  {
	 setJoinFields = value; 
	 conditionField = field;
	 parameterKey = paramKey;
  }
  
  private void setJoinFields(Join join, String conditionFieldValue )
  {
	 conditionField.setValue(conditionFieldValue, Field.DIRECTION.FROM_CLIENT);
	 Query query = new Query(Query.Type.SELECT);
	 WhereCondition[] wcs = new WhereCondition[1];
	 wcs[0] = new WhereCondition(conditionField, conditionField.getValue(), "=");
	 query.setWhereConditions(wcs);
	 query.setTables(join.getTables());
	 query.setFields(join.getFields());
	 db.setQuery(query);
//	 if(loggingEnabled)
//		 logger.debug("Editor.setJoinFields - query: " + query);
	 String[][] result = db.executeSelect();
	 Field[] fields = join.getFields();
	 for(int i = 0; i < result.length; i++)
	 {		 
		 for(int j = 0; j < result[0].length; j++)
		 {
			fields[j].setValue(result[i][j], Field.DIRECTION.FROM_CLIENT); 
		 } 
	 }
  }
  
    
  /**
   * Inquire if there are any instances of the Join class.
   * @return
   */
  protected boolean haveJoins()
  {
	  if(joins.size() > 0)
		  return true;
	  return false;
  }
  /**
   * Set an alias for the Parent Table.
   * @param value
   */
  public void setAliasParentTableName(String value)
  {
	  parentTableAlias = value;
  }
  /**
   * Get the alias of the Parent table	  
   * @return
   */
  public String getAliasParentTableName()
  {
	return parentTableAlias;
  }
  /**
   * Indicate that you want to include the Primary Key column
   * on the Parent table as part of your query.
   * @param value
   */
  public void IncludeRowId(boolean value)
  {
	  includeRowID = value;
  }
  /**
   * Inquire if the Primary Key column of the Parent table
   * has been set.
   * @return
   */
  public boolean IncludeRowId()
  {
	  return includeRowID;
  }
  /**
   * Indicate that you want to include a RowClass column
   * on the Parent table as part of your query. If setting 
   * to true, with this you will also need to call methods:
   * setRowclassPrefix(...) and setRowclassFieldReference(...).
   * @param value
   */
  public void IncludeRowClass(boolean value)
  {
	  includeRowClass = value;
  }
  /**
   * Inquire if RowClass has been included as part of the Parent table.
   * @return boolean
   */
  public boolean IncludeRowClass()
  {
	  return includeRowClass;
  }
  /**
   * If you set IncludeRowClass to true, you will need to
   * also set a prefix for the value that is returned.
   * @param value
   */
  public  void setRowclassPrefix(String value)
  {
	  rowclassPrefix = value;
  }
  /**
   * Get the RowClass prefix being used on the value returned.
   * @return
   */
  public String getRowclassPrefix()
  {
	  return rowclassPrefix;
  }
  /**
   * If you set IncludeRowClass to true, you will need to
   * also set a reference to one of the Fields.
   * @param value
   */
  public void setRowclassFieldReference(String value)
  {
	  rowclassFieldReference = value;
  }
  /**
   * Get the name of the Field being referenced for the RowClass.
   * @return
   */
  public String getRowclassFieldReference()
  {
	  return rowclassFieldReference;
  }
  /**
   * Set the Paramenters.
   * @param p
   */
  public void setParameters(Parameters p)
  {
	  params = p;
  }
  /**
   * Get the Parameters
   * @return Parameters
   */
  public Parameters getParameters()
  {
	  return params;
  }
  /**
   * Set the data. The data needs to be a 2D String array representing
   * a result from a query on the database.
   * @param value
   */
  public void setData(String[][] value)
  {
    data = value;
  }
  /**
   * Get the data.
   * @return
   */
  public String[][] getData()
  {
    return data;
  }
  /**
   * A convenience method to set an instance of the Database if one
   * wasn't set in the constructor of this class.
   * @param database
   */
  public void setDatabase(Database database)
  {
    db = database;
  }
  /**
   * Get the instance of the Database.
   * @return Database
   */
  public Database getDatabase()
  {
    return db;
  }
  /**
   * Set all the Fields required for the query on the database.
   * @param flds
   */
  public void setFields(ArrayList<Field> flds)
  {
    fields = flds;
  }
  /**
   * Add a Field required for the query on the database.
   * @param field
   */
  public void addField(Field field)
  {
	  if(fields == null)
	  {
		  fields = new ArrayList<Field>();
	  }
	  fields.add(field);
  }
  /**
   * Get all the fields.
   * @return
   */
  public ArrayList<Field> getFields()
  {
    return fields;
  }
  /**
   * Add a Join instance.
   * @param j
   */
  public void addJoin(Join j)
  {
    joins.add(j);
  }
  /**
   * Get all the Joins.
   * @return
   */
  public Join[] getJoins()
  {
	Join[] jns = new Join[joins.size()];
    for(int i = 0; i < joins.size(); i++)
    {
    	jns[i] = joins.get(i);
    }
    return jns;
  }
  /**
   * Call this method to obtain a JSON string representation of any of the 
   * output classes: CreateEditOutput, FieldErrorsOutput, NonSSPOutput, SSPOutput.
   * @return A JSON String
   */
  public String toJSONString()
  {
	String out = "";  
	if(output != null)
	{
	  Gson gson = new Gson();
	  out = gson.toJson(output);
	}
	
    return out;
  }
  /**
   * Set the Parent table name.
   * @param value
   */
  public void setTableName(String[] value)
  {
    table = value;
  }
  /**
   * Get the Parent table name.
   * @return
   */
  public String[] getTableName()
  {
    return table;
  }
  /**
   * Set the Primary Key column name on the Parent table.
   * @param value
   */
  public void setPrimaryKey(String value)
  {
    pKey = value;
  }
  /**
   * Get the Primary Key column name on the Parent table.
   * @return
   */
  public String getPrimaryKey()
  {
    return pKey;
  }
  
  private boolean allFieldsValid()
  {
	  boolean ok = true;
	  ValidationMessage vm = null;
	  Field field = null;
	  //Now, check that the value provided is valid on each field
	  for(int i = 0; i < fields.size(); i++)
	  {
		 field = fields.get(i);
		 if(field.hasValidator())
		 {
			if(field.hasDateFormat())
			{
			   vm = field.getValidator().isValid(field.getClientDateValue());
			}
			else
			{
			   //System.out.println("FieldName: " + field.getDBField() + " Value: " + field.getValue())
			   vm = field.getValidator().isValid(field.getValue().toString());
			}
			if(vm.isValid() == false)
			{
				//Set the fieldsError for the output
				FieldErrorsOutput err = new FieldErrorsOutput();
				LinkedHashMap<String,String> map = new LinkedHashMap<String,String>();
				map.put("name", field.getDBFieldName());
				map.put("status", vm.getMessage());
				err.fieldErrors.add(map);
				output = err;
				ok = false;
				break;
			}
		 }
	  }
	  if(haveJoins())
	  {
		  //Check the fields in the Joins as well.
		  Join join = null;	
		  Field[] fields = null;
		  START:
		  for(int i = 0; i < joins.size(); i++)
		  {
			 join = joins.get(i);
			 fields = join.getFields();
			 for(int j = 0; j < join.getFieldsCount(); j++)
			 {
				field = fields[j];
				if(field.canWriteDataToDatabase() && field.hasValidator())
				{
					if(field.hasDateFormat())
					{
					   vm = field.getValidator().isValid(field.getClientDateValue());
					}
					else
					{
					   //System.out.println("FieldName: " + field.getDBField() + " Value: " + field.getValue())
					   vm = field.getValidator().isValid(field.getValue().toString());
					}
					if(vm.isValid() == false)
					{
						//Set the fieldsError for the output
						FieldErrorsOutput err = new FieldErrorsOutput();
						LinkedHashMap<String,String> map = new LinkedHashMap<String,String>();
						map.put("name", field.getTableName() + "." + field.getName());
						map.put("status", vm.getMessage());
						err.fieldErrors.add(map);
						output = err;
						ok = false;
						break START;
					}
				}
			 }
		  }
	  }
	  return ok;
  }
  
  /**
   * Call this method to process your query.  
   */
  public void Process()
  {
	  try{
      String action = params.getAction();
      if(action != null && !action.equals(""))
      {
        if(action.equals(Constants.CREATE))
        {
          Field field = null;
          //Load the fields with values provided by params
     	  for(int i = 0; i < fields.size(); i++)
     	  {
     		  field = fields.get(i);
     		  if(field.canWriteDataToDatabase())
 	   		  {
     		    if(field.hasSubstituteField())
     		    {
     			  Field substitute = field.getSubstituteField(); 
     			  field.setValue(params.getDataValue(substitute.getTableName(), substitute.getName()), Field.DIRECTION.FROM_CLIENT);
     			  //The Join associated with this field would contain a lookup table - READ ONLY.
      			  //Therefore, inaccessible through other means. So we
      			  //override any restrictions on the Join and set its fields regardless.
      			  //We need to set its fields so that we get the proper output later.
      			  Join join = null;
          	      for(int j = 0; j < joins.size(); j++)
          	      {
          	   	    join = joins.get(j);
          	   	    if(join.getChildTableName().equalsIgnoreCase(substitute.getTableName()))
          	   	    {
          	   		  join.setFieldValues(db, params.getDataValue(substitute.getTableName(), substitute.getName()));
          	   		  break;
          	   	    }
          	      }
     		    }
     		    else
     		    {
     		      field.setValue(params.getDataValue(field.getName()), Field.DIRECTION.FROM_CLIENT);
     		      //System.out.println("Field name: " + field.getName() + " value: " + field.getValue());
     		    }
 	   		  }
     	  }	
     	  if(haveJoins())
          {
   		    Join join = null;
      	    for(int i = 0; i < joins.size(); i++)
      	    {
      	   	  join = joins.get(i);
      	   	  if(join.getCanWrite())
      	   	  {
      	   	    Field[] fields = join.getFields();
      	   	    for(int j = 0; j < fields.length; j++)
       	        {
      	   		  field = fields[j];
      	   		  //We may be dealing with a lookup table that is read only,
      	   		  //So ask if we can set the value on a field.
      	   		  //When an update is performed, we'll do it on the LINK table,
      	   		  //NOT the Child table.
      	   		  if(field.canWriteDataToDatabase())
      	   		  {
      	   		    field.setValue(params.getDataValue(field.getTableName(), field.getName()), Field.DIRECTION.FROM_CLIENT);
      	   		  }
       	        }
      	   	  }
      	    } 
          }
    	  boolean ok = allFieldsValid();
    	  if(ok == false)
    	  {
    		//Send back a JSON string with error message
    		//The Java Server Page should be calling the method
    		//Editor.toJSONString() to obtain a response.
    		//The method call allFieldsValid automatically
    		//populates the FieldErrorsOutput object with the appropriate
    		//FieldError.
    		return;
    	  }
    	  query = new Query(Query.Type.INSERT,table);
    	  query.setPrimaryKey(getPrimaryKey());
          db.setQuery(query);
                    
    	  insert();
    	  
//    	  if(loggingEnabled)
//    		  logger.debug("Editor.Process - insert query: " + query.toString());
    	  
    	  //PREPARE THE OUTPUT
    	  CreateEditOutput out = new CreateEditOutput();    	  
    	  field = new Field("", Constants.DT_ROWID, Field.Type.STRING);
    	  String recordID = query.getNewRecordID();
    	  field.setValue(Constants.IDPREFIX + recordID, Field.DIRECTION.FROM_CLIENT);
     	  fields.add(0, field);
     	  //out.id = Constants.IDPREFIX + recordID;
     	       	 
    	  for(int i = 0; i < this.fields.size(); i++)
    	  {
    		field = fields.get(i);
    		if(field.getExcludeOnOutput() == false)
    		{
    		  if(field.getFieldType() == Field.Type.DATE)
    			out.row.put(field.getName(), field.getClientDateValue());
    		  else
    			out.row.put(field.getName(), field.getValue().toString());
    		}
    	  }
    	  
    	  if(haveJoins())
  	       {
  	    	 Join join = null;
    	     for(int i = 0; i < joins.size(); i++)
    	     {
    	   	   join = joins.get(i);
    	   	   join.appendInsertUpdateOutput(db, out, params);
    	     }  
  	       }
    	  
    	  output = out;
    	  
        }
        else if(action.equals(Constants.REMOVE))
        {
          query = new Query(Query.Type.DELETE,table);
          db.setQuery(query);          
          remove();            
          CreateEditOutput out = new CreateEditOutput(); 
          output = out;
          
        }
        else if(action.equals(Constants.EDIT))
        {
           Field field = null;
       	   //Load the fields with values provided by params
    	   for(int i = 0; i < fields.size(); i++)
    	   {
    		  field = fields.get(i);
    		  if(field.canWriteDataToDatabase())
  	   		  {
    			  if(field.hasSubstituteField())
         		  {
         			 Field substitute = field.getSubstituteField(); 
         			 field.setValue(params.getDataValue(substitute.getTableName(), substitute.getName()), Field.DIRECTION.FROM_CLIENT);
         			 //The Join associated with this field would contain a lookup table - READ ONLY.
         			 //Therefore, inaccessible through other means. So we
         			 //override any restrictions on the Join and set its fields regardless.
         			 //We need to set its fields so that we get the proper output later.
         			 Join join = null;
             	     for(int j = 0; j < joins.size(); j++)
             	     {
             	   	  join = joins.get(j);
             	   	  if(join.getChildTableName().equalsIgnoreCase(substitute.getTableName()))
             	   	  {
             	   		join.setFieldValues(db, params.getDataValue(substitute.getTableName(), substitute.getName()));
             	   		break;
             	   	  }
             	    }
         		  }
         		  else
         		  {  
    		        field.setValue(params.getDataValue(field.getName()), Field.DIRECTION.FROM_CLIENT);
    		        //System.out.println("Field name: " + field.getName() + " value: " + field.getValue());
         		  }
  	   		  }
    	   }
    	   //Do the same in the Joins
    	   if(haveJoins())
    	   {
    		   Join join = null;
        	   for(int i = 0; i < joins.size(); i++)
        	   {
        	   	  join = joins.get(i);
        	   	  if(join.getCanWrite())
        	   	  {
        	   	    Field[] fields = join.getFields();
        	   	    for(int j = 0; j < fields.length; j++)
         	        {
        	   		  field = fields[j];
        	   		  //We may be dealing with a lookup table that is read only,
        	   		  //So ask if we can set the value on a field.
        	   		  //When an update is performed, we'll do it on the LINK table,
        	   		  //NOT the Child table.
        	   		  if(field.canWriteDataToDatabase())
        	   		  {
        	   		    field.setValue(params.getDataValue(field.getTableName(), field.getName()), Field.DIRECTION.FROM_CLIENT);
        	   		  }
         	        }
        	   	  }
        	   }
    	   }
    	   boolean ok = allFieldsValid();
      	   if(ok == false)
      	   {
      		  //Send back a JSON string with error message
     		  //The Java Server Page should be calling the method
     		  //Editor.toJSONString() to obtain a response.
     		  //The method call allFieldsValid automatically
     		  //populates the FieldErrorsOutput object with the appropriate
     		  //FieldError.
      		   return;
      	   }
      	   query = new Query(Query.Type.UPDATE,table);
           db.setQuery(query);
           
           String id = params.getID();
           //Calling update here also performs updates on any Join
      	   update(id);
//      	   if(loggingEnabled)
//      		 logger.debug("Editor.Process - update query: " + query.toString());
      	   
      	   //PREPARE THE OUTPUT
      	   CreateEditOutput out = new CreateEditOutput(); 
      	   //if(params.getID().indexOf(Constants.IDPREFIX) > -1)
      	   //	  out.id = params.getID();
      	   //else
      	   //   out.id = Constants.IDPREFIX + params.getID();
      	   field = new Field("", Constants.DT_ROWID,Field.Type.STRING);
      	   if(params.getID().indexOf(Constants.IDPREFIX) > -1)
      	      field.setValue(params.getID(), Field.DIRECTION.FROM_CLIENT);
      	   else
      		  field.setValue(Constants.IDPREFIX + params.getID(), Field.DIRECTION.FROM_CLIENT);
      	   fields.add(0, field);
      	   //LinkedHashMap<String,String> newrow = new LinkedHashMap<String,String>();
      	   for(int i = 0; i < this.fields.size(); i++)
   	       {
   		     field = fields.get(i); 
   		     if(field.getExcludeOnOutput() == false)
			 {
   		       if(field.getFieldType() == Field.Type.DATE)
   			     out.row.put(field.getName(), field.getClientDateValue());
   		       else
   		    	  out.row.put(field.getName(), field.getValue().toString());
			 }
   	       }
   	       if(haveJoins())
   	       {
   	    	 Join join = null;
     	     for(int i = 0; i < joins.size(); i++)
     	     {
     	   	   join = joins.get(i);
     	   	   join.appendInsertUpdateOutput(db, out, params);
     	     }  
   	       }
   	       output = out;   
        }        
      }
      else
      {
          
          
/*---------------------------------------------------------------------------------------------------------------------------------------*/
          
    	//THE ACTION IS A STRAIGHT SELECT QUERY 
    	query = new Query(Query.Type.SELECT,table);
    	
    	//Set the query object in the Database object
    	db.setQuery(query);    	
    	
        select();
        
        if(data == null)
        	return;
        
//        if(loggingEnabled)
//        	logger.debug("Editor.Process - select query: " + query.toString());
        
        //PROVIDE THE OUTPUT OBJECT WITH EVERYTHING IT REQUIRES TO SEND
	    //BACK AN APPROPRIATE RESPONSE.
        if(usingSSP)
        {
          SSPOutput out1 = new SSPOutput();
          if(params.getDraw() > -1)
  	      {
  	        out1.draw = params.getDraw();
  	        out1.recordsTotal = String.valueOf(query.getITotal());
  	        out1.recordsFiltered = String.valueOf(query.getIFilteredTotal());
  	      }
          if(includeRowID && includeRowClass == false)
  	      {
  	        Field field = new Field("",Constants.DT_ROWID,Field.Type.INT);
  	        fields.add(0,field);
  	      }
  	      else if(includeRowID && includeRowClass)
  	      {
  	        Field field1 = new Field("",Constants.DT_ROWID,Field.Type.INT);
  	        fields.add(0,field1);
  	        Field field2 = new Field("",Constants.DT_ROWCLASS,Field.Type.STRING);
  	        fields.add(1,field2);	      
  	      }
  	      else if(includeRowID == false && includeRowClass)
  	      {
  	        Field field = new Field("",Constants.DT_ROWCLASS,Field.Type.STRING);
  	        fields.add(0,field);	      
  	      }
          //PUT THE DATA INTO THE OUTPUT OBJECT FOR PROCESSING
          LinkedHashMap<String,Object> row = null;
  	      int cols = fields.size();
  	      String columnData = "";
  	      int index = 0;
  	      Field field = null;
  	      for(int i = 0; i < data.length; i++)
  	      {
  		    row = new LinkedHashMap<String,Object>();
  		    for(int j = 0; j < cols; j++)
  		    {
  		      if(j == 0 && includeRowID && includeRowClass == false)
  		      {
  		        row.put(fields.get(j).getName(),Constants.IDPREFIX + data[i][j]);
  		      }
  		      else if(j == 0 && includeRowID && includeRowClass)
  		      {
  		    	row.put(fields.get(j).getName(),Constants.IDPREFIX + data[i][j]);
  		      }
  		      else if(j == 1 && includeRowID && includeRowClass)
		      {
  		    	row.put(fields.get(j).getName(),rowclassPrefix + data[i][j]); 
		      }
  		      else if(j == 0 && includeRowID == false && includeRowClass)
  		      {
  		    	row.put(fields.get(j).getName(),rowclassPrefix + data[i][j]);  
  		      }
  		      else
  		      {
  		    	
  		    	columnData = params.getColumn(index).getData();  
  		    	field = findField(columnData); 
  		    	if(field.getExcludeOnOutput() == false)
				{
  		    	  if(field.getExcludeOnOutput() == false)
  		    	  {
  		    	    if(field.hasDateFormat())
  		    	    {
  		    	      field.setValue(data[i][j], Field.DIRECTION.FROM_DB);  		    		  
  		    	      row.put(columnData, field.getClientDateValue());  		    	  
  		    	    }
  		    	    else
  		    	      row.put(columnData ,data[i][j]);
  		    	  }
				}
  		    	
  		    	index++;
  		    			    	
  		      }
  		    }
  		    
  		    index = 0;//reset
  		    out1.addDataRow(row);
  	      }
  	      if(haveJoins())
  	      {
  	    	  Join join = null;
  	    	  for(int i = 0; i < joins.size(); i++)
  	    	  {
  	    		  join = joins.get(i);
  	    		  if(join.getCanRead())
  	    		  {
  	    			try{
  	    		      join.appendSelectJoinData(out1, db);
  	    			}
  	    			catch(UnsupportedOperationException uoe)
  	    			{
  	    				uoe.printStackTrace();
  	    			}
  	    		  }
  	    	  }
  	      }
  	      output = out1;
        }//end of usingSSP
        else
        {
          NonSSPOutput out2 = new NonSSPOutput();
          Field field = new Field("",Constants.DT_ROWID,Field.Type.INT);
	      fields.add(0,field);
          //PUT THE DATA INTO THE OUTPUT OBJECT FOR PROCESSING
          LinkedHashMap<String,Object> row = null;
  	      int cols = fields.size();
  	      Field field2 = null;
  	      for(int i = 0; i < data.length; i++)
  	      {
  		    row = new LinkedHashMap<String,Object>();
  		    for(int j = 0; j < cols; j++)
  		    {
  		      field2 = fields.get(j);
  		      if(field2.getExcludeOnOutput() == false)
	    	  {
  		        if(field2.hasDateFormat())
			    {
  		    	  field2.setValue(data[i][j], Field.DIRECTION.FROM_DB);
  		    	  row.put(field2.getName(),field2.getClientDateValue());
			    }
  		        else
  		        {
  		    	  if(j == 0)
  		    		row.put(fields.get(j).getName(),Constants.IDPREFIX + data[i][j]);
  		    	  else
  		            row.put(fields.get(j).getName(),data[i][j]);
  		        }
	    	  }
  		    }		    
  		    out2.addDataRow(row);
  	      }
  	      if(haveJoins())
	      {
	    	  Join join = null;
	    	  for(int i = 0; i < joins.size(); i++)
	    	  {
	    		  join = joins.get(i);
	    		  if(join.getCanRead())
  	    		  {
	    			try{
  	    		      join.appendSelectJoinData(out2, db);
	    			}
	    			catch(UnsupportedOperationException uoe)
  	    			{
  	    				uoe.printStackTrace();
  	    			}
  	    		  }
	    	  }
	      }
	      output = out2;
        }//end of using Non-SSP     
      }
    }
    catch(Exception e)
    {
//      if(loggingEnabled)
//    	 logger.error("Log4J error: " +  getFullStackTrace(e));      
    }    
  }
  //This method only works in conjunction with SSP when
  //assembling the appropriate field data for SSPOutput.
  private Field findField(String mDataProp)
  {
	  if(isNumeric(mDataProp))
	  {
		  if(includeRowID && includeRowClass == false)
		  {
		    int index = Integer.parseInt(mDataProp) + 1;
		    return fields.get(index);
		  }
		  else if(includeRowID && includeRowClass)
		  {
			  int index = Integer.parseInt(mDataProp) + 2;
			  return fields.get(index);  
		  }
		  else if(includeRowID == false && includeRowClass)
		  {
			  int index = Integer.parseInt(mDataProp) + 1;
			  return fields.get(index);  
		  }
		  else if(includeRowID == false && includeRowClass == false)
		  {
			  int index = Integer.parseInt(mDataProp);
			  return fields.get(index);  
		  }
	  }
	  Field field = null;
	  for(int i = 0; i < fields.size(); i++)
	  {
		  field = fields.get(i);
		  if(field.getDBFieldName().equals(mDataProp))
			  break;
	  }
	  return field;
  }
  
  private Field[] convertTo_FieldsArray(ArrayList<Field> fields)
  {
	  Field[] flds = new Field[fields.size()];
	  for(int i = 0; i < fields.size(); i++)
	  {
		  flds[i] = fields.get(i);
	  }
	  return flds;
  }
  
  private Field[] addPrimaryKeyField(ArrayList<Field> fields)
  {   
	  Field[] flds = new Field[fields.size() + 1];
	  flds[0] = new Field(table[0],pKey,Field.Type.INT);
	  int count = 1;
	  for(int i = 0; i < fields.size(); i++)
	  {
		  flds[count] = fields.get(i);
		  count++;
	  }
	  
	  return flds;
  }
  
  private Field[] addPrimaryKeyAndClassField(ArrayList<Field> fields) throws UnknownFieldException
  {
	  if(rowclassFieldReference.equals(""))
		  throw new UnknownFieldException("rowclassFieldReference is an empty string");
	  Field field = null;
	  //check that the rowclassFieldReference is actually referencing an existing field
	  boolean ok = false;
	  for(int i = 0; i < fields.size(); i++)
	  {
		 field = fields.get(i);
		 if(field.getDBFieldName().equalsIgnoreCase(rowclassFieldReference))
		 {
	       ok = true;
	       break;
		 }
	  }
	  if(ok == false)
	  {
		  throw new UnknownFieldException("rowclassFieldReference: " + rowclassFieldReference + " is not a field known to the existing list of fields.");  
	  }
	  
	  Field[] flds = new Field[fields.size() + 2];
	  flds[0] = new Field(table[0],pKey,Field.Type.INT);
	  field = null;
	  for(int i = 0; i < fields.size(); i++)
	  {
		 field = fields.get(i);
		 if(field.getDBFieldName().equalsIgnoreCase(rowclassFieldReference))
		 {
	       flds[1] = new Field("",field.getDBFieldName(),field.getFieldType());
	       break;
		 }
	  }
	  int count = 2;
	  for(int i = 0; i < fields.size(); i++)
	  {
		  flds[count] = fields.get(i);
		  count++;
	  }
	  
//	  if(loggingEnabled)
//	  {
//		logger.debug("Editor.addPrimaryKeyAndClassField - flds.length: " + flds.length);
//	    for(int i = 0; i < flds.length; i++)
//	    {
//		  if(flds[i] == null)
//			  logger.debug("Editor.addPrimaryKeyAndClassField - flds[" + i +"] is null");
//	    }
//	  }
	  
	  return flds;
  }
  
  private Field[] addClassField(ArrayList<Field> fields) throws UnknownFieldException
  {
	  if(rowclassFieldReference.equals(""))
		  throw new UnknownFieldException("rowclassFieldReference is an empty string");
	  Field field = null;
	  //check that the rowclassFieldReference is actually referencing an existing field
	  boolean ok = false;
	  for(int i = 0; i < fields.size(); i++)
	  {
		 field = fields.get(i);
		 if(field.getDBFieldName().equalsIgnoreCase(rowclassFieldReference))
		 {
	       ok = true;
	       break;
		 }
	  }
	  if(ok == false)
	  {
		  throw new UnknownFieldException("rowclassFieldReference: " + rowclassFieldReference + " is not a field known to the existing list of fields.");  
	  }
	  
	  Field[] flds = new Field[fields.size() + 1];
	  
	  field = null;
	  for(int i = 0; i < fields.size(); i++)
	  {
		 field = fields.get(i);
		 if(field.getDBFieldName().equalsIgnoreCase(rowclassFieldReference))
		 {
	       flds[0] = new Field("", field.getDBFieldName(),field.getFieldType());
	       break;
		 }
	  }
	  int count = 1;
	  for(int i = 0; i < fields.size(); i++)
	  {
		  flds[count] = fields.get(i);
		  count++;
	  }
	  	  
//	  if(loggingEnabled)
//	  {
//		logger.debug("Editor.addClassField - flds.length: " + flds.length);
	    for(int i = 0; i < flds.length; i++)
	    {
//		  if(flds[i] == null)
//			  logger.debug("Editor.addClassField - flds[" + i +"] is null");
	    }
//	  }
	  return flds;
  }
  
    
  private void select()
  {
	  //SET ALL THE INFORMATION WITHIN THE QUERY OBJECT
	  //SO THAT A PROPER QUERY CAN BE INVOKED ON THE DATABASE.
	  //The fields provided for the query need to include the name of the 
	  //primary key field.
	  query.setPrimaryKey(pKey);	  
  	  
	  if(params.getDraw() > -1)
	  {
		usingSSP = true;
	    ssp_sort(query);
	    ssp_filter(query);
	    ssp_limit(query);
	  }
	  if(usingSSP)
	  {
		  if(includeRowID && includeRowClass == false)
		  {
			  query.setFields(addPrimaryKeyField(fields));
		  }
		  else if(includeRowID && includeRowClass)
		  {
			  try{
			  query.setFields(addPrimaryKeyAndClassField(fields));
			  }
			  catch(UnknownFieldException ufe){ufe.printStackTrace();}
		  }
		  else if(includeRowID == false && includeRowClass)
		  {
			  try{
			  query.setFields(addClassField(fields));
			  }
			  catch(UnknownFieldException ufe){ufe.printStackTrace();}
		  }
		  else
		  {
		      query.setFields(convertTo_FieldsArray(fields));
		  }
		  
	  }
	  else
	  {
		//Primary key field is mandatory for non-ssp processing
	    query.setFields(addPrimaryKeyField(fields));	    
	  }
	  
	  //Include any WHERE conditions if the user is providing a filter directly in their query.
  	  if(where.size() > 0)
  	  {
  	    WhereCondition[] wc = new WhereCondition[where.size()];
  	    for(int i = 0; i < where.size(); i++)
  	    {
  		  wc[i] = where.get(i);
  	    }
  	    query.setWhereConditions(wc);
  	  }
  	  
	  //System.out.println("query: " + query.toString());
	  
	  data = db.executeSelect(); 
	  
  }

  
  private void insert()
  {
	  query.setFields(convertTo_FieldsArray(fields));
	  
	  boolean success = db.executeInsertUpdate();
	  //If the insert was unsuccessful, we should be
	  //sending an error response back to the client side.
	  if(success == false)
		  return;
	  
	  //Insert new record in the Joins as well.
 	  if(haveJoins())
      {
  	      Join join = null;
  	      for(int i = 0; i < joins.size(); i++)
  	      {
  	    	  join = joins.get(i);
  	    	  if(join.getCanWrite())
  	    	  {
  	    		  join.insert(db,query.getNewRecordID(), params);
  	    	  }
  	    	  if(setJoinFields)
	    	  {
	    		  setJoinFields(join, params.getDataValue(parameterKey));
	    	  }
  	      }
      }
	  
  }
  //When we perform an update, it is only on one row of data!
  private void update(String id)
  {
	  if(id.indexOf(Constants.IDPREFIX) > -1)
		 id = id.replace(Constants.IDPREFIX, "");	  	 
	  
	  //Let the query object know what fields we are acting on.
	  query.setFields(convertTo_FieldsArray(fields));
	  //In addition, create a new Field object representing the PRIMARY KEY
	  //field and set a Where object for the Query to act upon.
	  WhereCondition[] where = new WhereCondition[1];
	  Field pkfield = new Field("",pKey,Field.Type.INT);	  
	  where[0] = new WhereCondition(pkfield, Integer.parseInt(id), "=");
	  query.setWhereConditions(where);
	  
	  boolean success = db.executeInsertUpdate();
	  //If the update was unsuccessful, we should be
	  //sending an error response back to the client side.
	  if(success == false)
		  return;
	  
	  
	  //Update any changes in the Joins as well.
 	  if(haveJoins())
      {
  	      Join join = null;
  	      for(int i = 0; i < joins.size(); i++)
  	      {
  	    	  join = joins.get(i);
  	    	  if(join.getCanWrite())
  	    	  {
  	    		  join.update(db,id, params);
  	    	  }
  	    	  if(setJoinFields)
  	    	  {
  	    		  setJoinFields(join, params.getDataValue(parameterKey));
  	    	  }
  	      }
      }
	  
  }
  //NEEDS WORK
  //Delete one or more rows from the database
  private void remove()
  {
	try{
	  //formData = params.getDataValues();
	  formData = params.getIdValues();
			
	  //Print off the formData
	  //for(int i = 0; i < formData.length; i++)
	  //{
	  ///	System.out.println(formData[i] + " | ");
	  //}	  
	  String[] ids = new String[formData.length];  
	  // Strip the ID prefix that the client-side sends back which is "row_"
	  for(int i = 0; i < formData.length; i++)
	  {
		if(formData[i].indexOf(Constants.IDPREFIX) > -1)
		   ids[i] = formData[i].replace(Constants.IDPREFIX, "");
		//System.out.println("ids[" + i + "] = " + ids[i]);
	  }
	  
	  //To perform multiple deletes, we need multiple Query objects
	  //to pass to the Database.
	  //We already have one Query object floating around.  We need to
	  //create more, but with different Where objects so we can set the
	  //Primary Key values.
	
	  if(ids.length == 1)
	  { 
	    Query[] queries = new Query[1];
	    queries[0] = query;
	    WhereCondition[] where = new WhereCondition[1];
	    Field pkfield = new Field("",pKey,Field.Type.INT);
	    where[0] = new WhereCondition(pkfield, Integer.parseInt(ids[0]), "=");
	    query.setWhereConditions(where);
//	    if(loggingEnabled)
//	    	logger.debug("Editor.remove - delete query: " + query.toString());
	    db.executeDeletes(queries);	  
	  }
	  else
	  {
	    Query[] queries = new Query[ids.length];
	    queries[0] = query;
	    Query temp = null; //new Query(db,Query.Type.DELETE,table);
	    WhereCondition[] where = null;
	    Field pkfield = null;
	    //Create additional Query objects
	    for(int i = 1; i < ids.length; i++)
	    {
           temp = new Query(Query.Type.DELETE,table);
           queries[i] = temp;		
	    }
	    //Now set the Where objects for all of them
	    for(int i = 0; i < ids.length; i++)
	    {
           where = new WhereCondition[1];
           pkfield = new Field("",pKey,Field.Type.INT);
           where[0] = new WhereCondition(pkfield,Integer.parseInt(ids[i]),"=");
           queries[i].setWhereConditions(where);
//           if(loggingEnabled)
//        	   logger.debug("Editor.remove - delete query: " + queries[i].toString());
	    }
	    db.executeDeletes(queries);
	  }
	  if(haveJoins())
      {
  	    Join join = null;
  	    for(int i = 0; i < joins.size(); i++)
  	    {
  	      join = joins.get(i);
  	      if(join.getCanWrite())
  	         join.delete(db, ids);
  	    }
      }
	
	}
	catch(Exception e)
	{
////		if(loggingEnabled)
////	    	 logger.error("Log4J error: " +  getFullStackTrace(e)); 
	}
  }
  
  /**
   * Call this method if you want to apply a filter on your query.  
   * @param field
   * @param value
   * @param operand
   */
  public void appendWhere(Field field, Object value, String operand)
  {
	  where.add(new WhereCondition(field,value,operand));
  }
  /**
   * Get all the Where conditions on your query
   * @return
   */
  public ArrayList<WhereCondition> getWhere()
  {
    return where;
  }
  
  
  private String ssp_field(int index) 
  {	  
	  return fields.get(index).getDBFieldName();	  
  }
  
  private void ssp_sort(Query query)
  {
	  ArrayList<Order> orders = new ArrayList<Order>();
	  int cols = params.getColumnOrdersSize();
	  Order temp = null;
	  for(int i = 0; i < cols; i++)
	  {
		  //if(params.getSortableColumn("bSortable_"+ i) == true)
		  if(params.getColumn(i).getOrderable())
		  {
			  //temp = new Order(ssp_field(params.getISortColumn("iSortCol_" + i)), params.getSSortDir("sSortDir_" + i));
			  temp = new Order(ssp_field(params.getColumnOrder(i).getColumn()),params.getColumnOrder(i).getDirection().toString());
			  orders.add(temp);
		  }
	  }
	  if(orders.size() > 0)
	  {
	    Order[] queryOrders = new Order[orders.size()];
	    for(int i = 0; i < orders.size(); i++)
	    {
		  queryOrders[i] = orders.get(i);
	    }
	    query.setOrder(queryOrders);
	  }
	  
  }
  
  private void ssp_filter(Query query)
  {
	  int columnsSize = params.getColumns().size();
	  
	  //FILTER ON ALL FIELDS
	  Field field = null;
	  Column col = null;
	  if(!params.getSearchValue().equals(""))
	  {  
		//This will be a global search on all fields.
		//Therefore, set the FilterLogicalOperator to OR
		query.setFilterLogicalOperator(Query.FilterLogicalOperator.OR);
		WhereCondition[] where = new WhereCondition[params.getColumns().size()];
		
		for(int i = 0; i < columnsSize; i++)
		{
			col = params.getColumn(i);
			if(col.getSearchable())
			{
			  field = fields.get(i);
			  where[i] = new WhereCondition(field, "%" + params.getSearchValue() + "%" , " LIKE ");
			}
		}
		query.setWhereConditions(where);
		return;
	  }
	  //INDIVIDUAL COLUMN FILTERING
	  query.setFilterLogicalOperator(Query.FilterLogicalOperator.AND);
	  ArrayList<WhereCondition> list = new ArrayList<WhereCondition>();
	  String searchValue = "";	  
	  for(int i = 0; i < columnsSize; i++)
	  {
		 col = params.getColumn(i);
		 searchValue = col.getSearchValue(); 
		 if(searchValue != null && !searchValue.equals("") && col.getSearchable())
		 {
			 field = fields.get(i);
			 list.add(new WhereCondition(field,"%" + searchValue + "%", " LIKE "));	
		 }		 
	  }
	  if(list.size() > 0)
	  {
	    WhereCondition[] where = convertToArray(list);
	    query.setWhereConditions(where);
	  }
  }
  
  private WhereCondition[] convertToArray(ArrayList<WhereCondition> list)
  {
	  WhereCondition[] where = new WhereCondition[list.size()];
	  for(int i = 0;  i < list.size(); i++)
	  {
		  where[i] = list.get(i);
	  }
	  return where;
  }
  
  private void ssp_limit(Query query)
  {
	  //-1 is show all
	  if(params.getLength() != -1)
	  {
		  query.setOffset(params.getStart());
		  query.setLimit(params.getLength());
	  }
  }
  /**
   * Use this method to append a JSON string to an existing one.
   * This method assumes that the first parameter json1 is a complete
   * JSON representation of some data structure and the second parameter
   * is represents some object structure. Usage:<br>
   * String json1 = &quot;{ "data1":[...] }&quot;<br>
   * String json2 = &quot;{ "data2":[...] }&quot;<br>
   * String result = appendJSONData(json1, json2);<br>
   * System.out.println(result);<br>
   * {"data1":[...],"data2"[...]}<br>
   * @param json1
   * @param json2
   * @return A complete JSON string resultant from both JSON parameters.
   */
  public String appendJSONData(String json1, String json2)
  {
	  //Remove the last character from the first JSON string
	  char lastChar = json1.charAt(json1.length() - 1);
	  //Make json1 contain everything except the last character
	  //We'll append it later.
	  json1 = json1.substring(0,json1.length() - 1);
	  //add a comma on the end of json1
	  json1 += ",";
	  //Strip away both the first and last character from json2
	  json2 = json2.substring(1,json2.length() - 1);
	  //Add json2 to json1
	  json1 += json2;
	  json1 += lastChar;
	  return json1;
	  
  }
  
  /**
   * This is just a convenience method to run a simple SELECT query
   * on just two fields in a table.  
   * @param tableName
   * @param field1
   * @param field2
   * @return A 2D String array
   */
  public String[][] runSelectQuery(String tableName, String field1, String field2)
  {
	if(db == null)return new String[0][0];
	String query = "SELECT " + field1 + "," + field2 + " FROM " + tableName;
	return db.executeSelect(query, 2);	 
  }
  /**
   * This is just a convenience method to run a simple SELECT query
   * on just three fields in a table.
   * @param tableName
   * @param field1
   * @param field2
   * @param field3
   * @return A 2D String array
   */
  public String[][] runSelectQuery(String tableName, String field1, String field2, String field3)
  {
    if(db == null)return new String[0][0];  
	String query = "SELECT " + field1 + "," + field2 + "," + field3 + " FROM " + tableName;
	return db.executeSelect(query, 3);	 
  }
  /**
   * This is just a convenience method to run a simple SELECT query
   * on just four fields in a table.
   * @param tableName
   * @param field1
   * @param field2
   * @param field3
   * @param field4
   * @return A 2D String array
   */
  public String[][] runSelectQuery(String tableName, String field1, String field2, String field3, String field4)
  {
	if(db == null)return new String[0][0];
	String query = "SELECT " + field1 + "," + field2 + "," + field3 + "," + field4 + " FROM " + tableName;
	return db.executeSelect(query, 4);	 
  }
  
  private boolean isNumeric(String value)
  {
	  char ch;
	  for(int i = 0; i < value.length(); i++ )
	  {
		 ch = value.charAt(i);
		 if(!Character.isDigit(ch))
		 {
			 return false; 
		 }
	  }
	  return true;  
  }
  
}


